<?php
namespace tpfcore\storage;
use OSS\OssClient;
use OSS\Core\OssException;
/**
 * ============================================================================
 * 版权所有 2017-2077 tpframe工作室，并保留所有权利。
 * @link http://www.tpframe.com/
 * @author    yaosean <510974211>
 * @copyright Copyright (c) 2017 TPFrame Software LLC
 * ----------------------------------------------------------------------------
 * 这不是一个自由软件！未经本公司授权您只能在不用于商业目的的前提下对程序代码进行修改和使用；
 * 不允许对程序代码以任何形式任何目的的再发布。
 * ============================================================================
 * tpf标签主类
 */
class AliyunOss{
	private $ossClient=null;
	public $sdkinfo=[
		"accessKeyId"=>"",
		"accessKeySecret"=>"",
		"endpoint"=>"",
		"bucket"=>"",
		"oss_img_url"=>""
	];
	private $config =   [
	    'maxSize'           =>  2097152,    		// 上传文件的最大值,默认2M
	    'supportMulti'      =>  true,    	// 是否支持多文件上传
	    'allowExts'         =>  array("jpg,png,gif"),    // 允许上传的文件后缀 留空不作后缀检查
	    'allowTypes'        =>  array(),    // 允许上传的文件类型 留空不做检查
	    'thumb'             =>  false,    	// 使用对上传图片进行缩略图处理
	    'thumbMaxWidth'     =>  '800',			// 缩略图最大宽度
	    'thumbMaxHeight'    =>  '600',			// 缩略图最大高度
	    'thumbPrefix'       =>  'thumb_',	// 缩略图前缀
	    'thumbSuffix'       =>  '',
	    'thumbPath'         =>  '',			// 缩略图保存路径
	    'thumbFile'         =>  '',			// 缩略图文件名
	    'thumbExt'          =>  '',			// 缩略图扩展名
	    'thumbRemoveOrigin' =>  false,		// 是否移除原图
	    'zipImages'         =>  false,		// 压缩图片文件上传
	    'autoSub'           =>  false,		// 启用子目录保存文件
	    'subType'           =>  'hash',		// 子目录创建方式 可以使用hash date custom
	    'subDir'            =>  '', 		// 子目录名称 subType为custom方式后有效
	    'dateFormat'        =>  'Ymd',
	    'hashLevel'         =>  1, 			// hash的目录层次
	    'savePath'          =>  '',	// 上传文件保存路径
	    'autoCheck'         =>  true, 		// 是否自动检查附件
	    'uploadReplace'     =>  false,		// 存在同名是否覆盖
	    'saveRule'          =>  'uniqid',	// 上传文件命名规则
	    'hashType'          =>  'md5_file',	// 上传文件Hash规则函数名
    ];

    public $error;

    public $uploadFileInfo;

    public function __set($name,$value){
    	isset($this->config[$name]) && $this->config[$name]=$value;
    }

    public function __get($name){
    	return isset($this->config[$name])?$this->config[$name]:null;
    }

    public function __issete($name){
    	return isset($this->config[$name]);
    }

    public function __unset($name){
    	unset($this->config[$name]);
    }

    public function __construct($sdkinfo=[]){
    	if(is_array($sdkinfo) && !empty($sdkinfo)){
    		$this->sdkinfo = array_merge($this->sdkinfo,$sdkinfo);
    	}

    	if(empty($this->sdkinfo['accessKeyId']) || empty($this->sdkinfo['accessKeySecret'])){

    		throw new \Exception("请先配置好accessKeyId或accessKeySecret后再试");
    		
    	}

    	if(empty($this->sdkinfo['endpoint'])){
    		throw new \Exception("存储空间域名必须指定");
    	}

    	try{
    		$this->ossClient=new OssClient($this->sdkinfo['accessKeyId'], $this->sdkinfo['accessKeySecret'], $this->sdkinfo['endpoint']);
    	}catch(OssException $e){
    		throw new \Exception($e->getMessage());
    	}
    	
    }
    /**
	 * 把本地变量的内容到文件
	 * 简单上传,上传指定变量的内存值作为object的内容
	 */
	public function putObject($imgPath,$object)
	{   
	    $content = file_get_contents($imgPath); // 把当前文件的内容获取到传入文件中
	    $options = array();
	    try {
	        $this->ossClient->putObject($this->sdkinfo['bucket'], $object, $content, $options);
	    } catch (OssException $e) {
	        return $e->getMessage();
	    }
	    return true;
	}

	/**
	 * 上传指定的本地文件内容
	 */
	public function uploadFile($imgPath,$object)
	{

	    $filePath  = $imgPath;
	    $options = array();
	    if(!$this->uploadReplace && $this->doesObjectExist($object)) {
	        // 不覆盖同名文件
	        $this->error    =   '文件已经存在！'.$object;
	        return false;
	    }
	    try {
	        $this->ossClient->uploadFile($this->sdkinfo['bucket'], $object, $filePath, $options);
	    } catch (OssException $e) {
	        $this->error = $e->getMessage();
	        return false;
	    }

	    $file['extension']  =   $this->getPathInfo($imgPath,'extension');
        $file['savepath']   =   $this->getPathInfo($object,'dirname');
        $file['savename']   =   $this->getPathInfo($object,'filename').".".$file['extension'];
        $this->uploadFileInfo = $file;

	    return true;
	}

	// 删除对象
	public function deleteObject($object) {
	    try {
	        $this->ossClient->deleteObject($this->sdkinfo['bucket'], $object);
	    } catch (OssException $e) {
	        return $e->getMessage();
	    }
	    return true;
	}

	// 判断对象是否存在
	public function doesObjectExist($object) {
	    try {
	        $result = $this->ossClient->doesObjectExist($this->sdkinfo['bucket'], $object);
	    } catch (OssException $e) {
	        $this->error = $e->getMessage();
	        return true;
	    }
	    return $result;
	}

	// 批量删除对象
	public function deleteObjects($objects) {
	    try {
	        $this->ossClient->deleteObjects($this->sdkinfo['bucket'], $objects);
	    } catch (OssException $e) {
	        return $e->getMessage();
	    }
	    return true;
	}

	/**
	 * 获取object的内容
	 *
	 * @param OssClient $ossClient OssClient实例
	 * @param string $bucket 存储空间名称
	 * @return null
	 */
	public function getObject($object)
	{
	    $options = array();
	    try {
	        $content = $this->ossClient->getObject($this->sdkinfo['bucket'], $object, $options);
	    } catch (OssException $e) {
	        return $e->getMessage();
	    }
	    return $content;
	}

	/**
	 * 上传所有文件
	 * @access public
	 * @param string $savePath  上传文件保存路径
	 * @return string
	 */
	public function upload($savePath ='') {
	    //如果不指定保存文件名，则由系统默认
	    if(empty($savePath)) {
	        $savePath = $this->savePath;
	    }

	    $fileInfo   = array();
	    $isUpload   = false;

	    // 获取上传的文件信息
	    // 对$_FILES数组信息处理
	    $files   =   $this->dealFiles($_FILES);
	    foreach($files as $key => $file) {
	        //过滤无效的上传
	        if(!empty($file['name'])) {
	            //登记上传文件的扩展信息
	            if(!isset($file['key']))   $file['key']    =   $key;
	            $file['extension']  =   $this->getExt($file['name']);
	            $file['savepath']   =   $savePath;
	            $file['savename']   =   date("YmdHis").(microtime()*1000000).".".$file['extension'];

	            // 自动检查附件
	            if($this->autoCheck) {
	                if(!$this->check($file))
	                    return false;
	            }

	            //保存上传文件
	            if(!$this->save($file)) return false;
	            if(function_exists($this->hashType)) {
	                $fun =  $this->hashType;
	                $file['hash']   =  $fun($this->autoCharset($file['savepath'].$file['savename'],'utf-8','gbk'));
	            }
	            //上传成功后保存文件信息，供其他地方调用
	            unset($file['tmp_name'],$file['error']);
	            $fileInfo[] = $file;
	            $isUpload   = true;
	        }
	    }
	    if($isUpload) {
	        $this->uploadFileInfo = $fileInfo;
	        return true;
	    }else {
	        $this->error  =  '没有选择上传文件';
	        return false;
	    }
	}

	/**
	 * 上传一个文件
	 * @access public
	 * @param mixed $name 	数据
	 * @param string $value  数据表名
	 * @return string
	 */
	private function save($file) {
	    $filename = $file['savepath'].$file['savename'];
	    if(!$this->uploadReplace && $this->doesObjectExist($filename)) {
	        // 不覆盖同名文件
	        $this->error    =   '文件已经存在！'.$filename;
	        return false;
	    }
	    // 如果是图像文件 检测文件格式
	    if( in_array(strtolower($file['extension']),array('gif','jpg','jpeg','bmp','png','swf'))) {
	        $info   = getimagesize($file['tmp_name']);
	        if(false === $info || ('gif' == strtolower($file['extension']) && empty($info['bits']))){
	            $this->error = '非法图像文件';
	            return false;                
	        }
	    }
	    if(!$this->putObject($file['tmp_name'], $this->autoCharset($filename,'utf-8','gbk'))) {
	        $this->error = '文件上传保存错误！';
	        return false;
	    }
	    if($this->thumb && in_array(strtolower($file['extension']),array('gif','jpg','jpeg','bmp','png'))) {
	        $image =  getimagesize($this->sdkinfo['oss_img_url'].'/'.$filename);
	        if(false !== $image) {
	            //是图像文件生成缩略图
	            $thumbWidth     =   explode(',',$this->thumbMaxWidth);
	            $thumbHeight    =   explode(',',$this->thumbMaxHeight);
	            $thumbPrefix    =   explode(',',$this->thumbPrefix);
	            $thumbSuffix    =   explode(',',$this->thumbSuffix);
	            $thumbFile      =   explode(',',$this->thumbFile);
	            $thumbPath      =   $this->thumbPath?$this->thumbPath:dirname($filename).'/';
	            $thumbExt       =   $this->thumbExt ? $this->thumbExt : $file['extension']; //自定义缩略图扩展名
	            // 生成图像缩略图
	            import($this->imageClassPath);
	            for($i=0,$len=count($thumbWidth); $i<$len; $i++) {
	                if(!empty($thumbFile[$i])) {
	                    $thumbname  =   $thumbFile[$i];
	                }else{
	                    $prefix     =   isset($thumbPrefix[$i])?$thumbPrefix[$i]:$thumbPrefix[0];
	                    $suffix     =   isset($thumbSuffix[$i])?$thumbSuffix[$i]:$thumbSuffix[0];
	                    $thumbname  =   $prefix.basename($filename,'.'.$file['extension']).$suffix;
	                }
	                $this->thumb($this->sdkinfo['oss_img_url'].'/'.$filename,$thumbPath.$thumbname.'.'.$thumbExt,'',$thumbWidth[$i],$thumbHeight[$i],true);                    
	            }
	            if($this->thumbRemoveOrigin) {
	                // 生成缩略图之后删除原图
	                $this->deleteObject($filename);
	            }
	        }
	    }
	    if($this->zipImags) {
	        // TODO 对图片压缩包在线解压

	    }
	    return true;
	}

	/**
	 * 生成缩略图
	 * @static
	 * @access public
	 * @param string $image  原图
	 * @param string $type 图像格式
	 * @param string $thumbname 缩略图文件名
	 * @param string $maxWidth  宽度
	 * @param string $maxHeight  高度
	 * @param string $position 缩略图保存目录
	 * @param boolean $interlace 启用隔行扫描
	 * @return void
	 */
	public function thumb($image, $thumbname, $type='', $maxWidth=200, $maxHeight=50, $interlace=true) {
	    // 获取原图信息
	    $info = Image::getImageInfo($image);
	    if ($info !== false) {
	        $srcWidth = $info['width'];
	        $srcHeight = $info['height'];
	        $type = empty($type) ? $info['type'] : $type;
	        $type = strtolower($type);
	        $interlace = $interlace ? 1 : 0;
	        unset($info);
	        $scale = min($maxWidth / $srcWidth, $maxHeight / $srcHeight); // 计算缩放比例
	        if ($scale >= 1) {
	            // 超过原图大小不再缩略
	            $width = $srcWidth;
	            $height = $srcHeight;
	        } else {
	            // 缩略图尺寸
	            $width = (int) ($srcWidth * $scale);
	            $height = (int) ($srcHeight * $scale);
	        }

	        // 载入原图
	        $createFun = 'ImageCreateFrom' . ($type == 'jpg' ? 'jpeg' : $type);
	        if(!function_exists($createFun)) {
	            return false;
	        }
	        $srcImg = $createFun($image);


	        //创建缩略图
	        if ($type != 'gif' && function_exists('imagecreatetruecolor'))
	            $thumbImg = imagecreatetruecolor($width, $height);
	        else
	            $thumbImg = imagecreate($width, $height);
	          //png和gif的透明处理 by luofei614
	        if('png'==$type){
	            imagealphablending($thumbImg, false);//取消默认的混色模式（为解决阴影为绿色的问题）
	            imagesavealpha($thumbImg,true);//设定保存完整的 alpha 通道信息（为解决阴影为绿色的问题）
	        }elseif('gif'==$type){
	            $trnprt_indx = imagecolortransparent($srcImg);
	             if ($trnprt_indx >= 0) {
	                    //its transparent
	                   $trnprt_color = imagecolorsforindex($srcImg , $trnprt_indx);
	                   $trnprt_indx = imagecolorallocate($thumbImg, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue']);
	                   imagefill($thumbImg, 0, 0, $trnprt_indx);
	                   imagecolortransparent($thumbImg, $trnprt_indx);
	          }
	        }
	        // 复制图片
	        if (function_exists("ImageCopyResampled"))
	            imagecopyresampled($thumbImg, $srcImg, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight);
	        else
	            imagecopyresized($thumbImg, $srcImg, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight);

	        // 对jpeg图形设置隔行扫描
	        if ('jpg' == $type || 'jpeg' == $type)
	            imageinterlace($thumbImg, $interlace);

	        imagePNG($thumbImg,'Uploads/file.png'); // 中转站
	        // 生成图片
	        $this->putObject('Uploads/file.png',$thumbname);
	        imagedestroy($thumbImg);
	        imagedestroy($srcImg);
	        return $thumbname;
	    }
	    return false;
	}
	/**
     * 转换上传文件数组变量为正确的方式
     * @access private
     * @param array $files  上传的文件变量
     * @return array
     */
    private function dealFiles($files) {
        $fileArray  = array();
        $n          = 0;
        foreach ($files as $key=>$file){
            if(is_array($file['name'])) {
                $keys       =   array_keys($file);
                $count      =   count($file['name']);
                for ($i=0; $i<$count; $i++) {
                    $fileArray[$n]['key'] = $key;
                    foreach ($keys as $_key){
                        $fileArray[$n][$_key] = $file[$_key][$i];
                    }
                    $n++;
                }
            }else{
               $fileArray[$key] = $file;
            }
        }
       return $fileArray;
    }

    /**
     * 检查上传的文件
     * @access private
     * @param array $file 文件信息
     * @return boolean
     */
    private function check($file) {
        if($file['error']!== 0) {
            //文件上传失败
            //捕获错误代码
            $this->error($file['error']);
            return false;
        }
        //文件上传成功，进行自定义规则检查
        //检查文件大小
        if(!$this->checkSize($file['size'])) {
            $this->error = '上传文件大小不符！';
            return false;
        }

        //检查文件Mime类型
        if(!$this->checkType($file['type'])) {
            $this->error = '上传文件MIME类型不允许！';
            return false;
        }
        //检查文件类型
        if(!$this->checkExt($file['extension'])) {
            $this->error ='上传文件类型不允许';
            return false;
        }

        //检查是否合法上传
        if(!$this->checkUpload($file['tmp_name'])) {
            $this->error = '非法上传文件！';
            return false;
        }
        return true;
    }

    // 自动转换字符集 支持数组转换
    private function autoCharset($fContents, $from='gbk', $to='utf-8') {
        $from   = strtoupper($from) == 'UTF8' ? 'utf-8' : $from;
        $to     = strtoupper($to) == 'UTF8' ? 'utf-8' : $to;
        if (strtoupper($from) === strtoupper($to) || empty($fContents) || (is_scalar($fContents) && !is_string($fContents))) {
            //如果编码相同或者非字符串标量则不转换
            return $fContents;
        }
        if (function_exists('mb_convert_encoding')) {
            return mb_convert_encoding($fContents, $to, $from);
        } elseif (function_exists('iconv')) {
            return iconv($from, $to, $fContents);
        } else {
            return $fContents;
        }
    }


    /**
     * 检查上传的文件类型是否合法
     * @access private
     * @param string $type 数据
     * @return boolean
     */
    private function checkType($type) {
        if(!empty($this->allowTypes))
            return in_array(strtolower($type),$this->allowTypes);
        return true;
    }


    /**
     * 检查上传的文件后缀是否合法
     * @access private
     * @param string $ext 后缀名
     * @return boolean
     */
    private function checkExt($ext) {
        if(!empty($this->allowExts))
            return in_array(strtolower($ext),$this->allowExts,true);
        return true;
    }

    /**
     * 检查文件大小是否合法
     * @access private
     * @param integer $size 数据
     * @return boolean
     */
    private function checkSize($size) {
        return !($size > $this->maxSize) || (-1 == $this->maxSize);
    }

    /**
     * 检查文件是否非法提交
     * @access private
     * @param string $filename 文件名
     * @return boolean
     */
    private function checkUpload($filename) {
        return is_uploaded_file($filename);
    }

    /**
     * 取得上传文件的后缀
     * @access private
     * @param string $filename 文件名
     * @return boolean
     */
    private function getExt($filename) {
        $pathinfo = pathinfo($filename);
        return $pathinfo['extension'];
    }
    /**
     * 取得文件基本信息
     * @access private
     * @param string $filename 文件名
     * @return boolean
     */
    private function getPathInfo($filename,$key=""){
    	if(empty($key)){
    		return pathinfo($filename);
    	}else{
    		$pathinfo = pathinfo($filename);
    		return $pathinfo[$key];
    	}
    }

    /**
     * 取得上传文件的信息
     * @access public
     * @return array
     */
    public function getUploadFileInfo() {
        return $this->uploadFileInfo;
    }

    /**
     * 取得最后一次错误信息
     * @access public
     * @return string
     */
    public function getErrorMsg() {
        return $this->error;
    }
    /**
     * 创建bucket
     * @param int $type 存储类型
     * @param int $permissions 读写权限
     * @access public
     * @return string
     */
    public function createBucket($bucket,$type=1,$permissions=1){
    	// 设置存储空间的存储类型为低频访问类型，默认是标准类型。
	    switch ($type) {
	    	case 1:
	    		$options = [OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_STANDARD];
	    		break;
	    	case 2:
	    		$options = [OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_IA];
	    		break;
	    	case 3:
	    		$options = [OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_ARCHIVE];
	    		break;
	    	default:
	    		$options = [OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_STANDARD];
	    		break;
	    }

	    switch ($permissions) {
	    	case 1:
	    		$permissions = OssClient::OSS_ACL_TYPE_PRIVATE;
	    		break;
	    	case 2:
	    		$permissions = OssClient::OSS_ACL_TYPE_PUBLIC_READ;
	    		break;
	    	case 3:
	    		$permissions = OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE;
	    		break;
	    	default:
	    		$permissions = OssClient::OSS_ACL_TYPE_PRIVATE;
	    		break;
	    }

	    try {
	    	 $this->ossClient->createBucket($bucket, $permissions, $options);
	    } catch (Exception $e) {
	    	$this->error = $e->getMessage();
	        return false;
	    }
	    return true;
    }
    /**
     * 判断bucket是否存在
     * @access public
     * @return string
     */
    public function doesBucketExist($bucket){
    	try {
	        $result = $this->ossClient->doesBucketExist($bucket);
	    } catch (OssException $e) {
	        $this->error = $e->getMessage();
	        return false;
	    }
	    return true;
    }
    /**
     * 列举存储空间
     * @access public
     * @return string
     */
    public function listBuckets(){
    	try {
    		$bucketListInfo = $this->ossClient->listBuckets();
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	$bucketList = $bucketListInfo->getBucketList();
    	return $bucketList;
    }
    /**
     * 获取存储空间的访问权限
     * @access public
     * @return string
     */
    public function getBucketAcl($bucket){
    	try {
    		$bucketListInfo = $this->ossClient->getBucketAcl($bucket);
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	return $bucketListInfo;
    }
    /**
     * 删除存储空间
     * @access public
     * @return string
     */
    public function deleteBucket($bucket){
    	try {
    		$this->ossClient->deleteBucket($bucket);
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	return true;
    }
    /**
     * 获取存储空间的地域
     * @access public
     * @return string
     */
    public function getBucketLocation($bucket){
    	try {
    		$regions = $this->ossClient->getBucketLocation($bucket);
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	return (array)$regions;
    }
    /**
     * 获取存储空间元信息
     * @access public
     * @return string
     */
    public function getBucketMeta($bucket){
    	try {
    		$metas = $this->ossClient->getBucketMeta($bucket);
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	return $metas;
    }
    /**
     * 设置存储空间的访问权限
     * @access public
     * @return string
     */
    public function putBucketAcl($bucket, $permissions){
    	switch ($permissions) {
	    	case 1:
	    		$permissions = OssClient::OSS_ACL_TYPE_PRIVATE;
	    		break;
	    	case 2:
	    		$permissions = OssClient::OSS_ACL_TYPE_PUBLIC_READ;
	    		break;
	    	case 3:
	    		$permissions = OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE;
	    		break;
	    	default:
	    		$permissions = OssClient::OSS_ACL_TYPE_PRIVATE;
	    		break;
	    }
	    try {
    		$result = $this->ossClient->putBucketAcl($bucket, $permissions);
    	} catch (Exception $e) {
    		$this->error = $e->getMessage();
	        return false;
    	}
    	return true;
    }
}